﻿using System;
using System.Collections.Generic;
using System.Text;
using System.DirectoryServices;

namespace AzManADAMStore
{
    /// <summary>
    /// Gestor de Azman sobre un ADAM
    /// </summary>
    public class AzManMgr
    {
        #region " Constantes "

        /// <summary>
        /// Tipo de objeto para los grupos
        /// </summary>
        protected const string OBJECT_APPLICATION = "msDS-AzApplication";

        /// <summary>
        /// Tipo de objeto para los grupos
        /// </summary>
        protected const string OBJECT_GROUP = "group";

        /// <summary>
        /// Usuario de ADAM
        /// </summary>
        protected const string OBJECT_USER = "user";

        /// <summary>
        /// Tipo de objecto Rol
        /// </summary>
        protected const string OBJECT_ROLE = "msDS-AzRole";

        /// <summary>
        /// Tipo de objeto tarea
        /// </summary>
        protected const string OBJECT_TASK = "msDS-AzTask";

        /// <summary>
        /// Tipo de objeto operación
        /// </summary>
        protected const string OBJECT_OPERATION = "msDS-AzOperation";

        /// <summary>
        /// Atributo con el nombre de la aplicación
        /// </summary>
        protected const string ATTRIBUTE_APPLICATION_NAME = "msDS-AzApplicationName";

        /// <summary>
        /// Atributo de un grupo que indica a que roles está asociado
        /// </summary>
        protected const string ATTRIBUTE_GROUP_ROLEASIGNADO = "msDS-MembersForAzRoleBL";

        /// <summary>
        /// Atributo de un rol que indica a que usuarios está asociado
        /// </summary>
        protected const string ATTRIBUTE_ROLE_USERASIGNADO = "msDS-MembersForAzRole";

        /// <summary>
        /// Atrib uto que indica los roles hijos
        /// </summary>
        protected const string ATTRIBUTE_ROLESHIJOS = "msDS-TasksForAzTask";

        /// <summary>
        /// Atributo que indica los roles asignados
        /// </summary>
        protected const string ATTRIBUTE_ROLESASIGNADOS = "msDS-TasksForAzRole";

        /// <summary>
        /// Atributo de grupos asignado
        /// </summary>
        protected const string ATTRIBUTE_ROLE_GROUPSASIGNADO = "msDS-MembersForAzRole";

        /// <summary>
        /// Atributo con la consulta LDAP del grupo
        /// </summary>
        protected const string ATTRIBUTE_GROUP_LDAPQUERY = "msDS-AzLDAPQuery";

        protected const string ATTRIBUTE_TASKASIGNADOS = "msDS-OperationsForAzTask";

        #endregion

        /// <summary>
        /// Cadena de conexión al ADAM
        /// </summary>
        private string _ConnectionString;

        /// <summary>
        /// Nombre de aplicación
        /// </summary>
        private string _ApplicationName;

        /// <summary>
        /// Usuario para logarse contra ADAM
        /// </summary>
        private string _UserNameToLogon;

        /// <summary>
        /// Password para logarse contra ADAM
        /// </summary>
        private string _PasswordToLogon;

        /// <summary>
        /// Atributo por el que se buscará un usuario
        /// </summary>
        private string _UserAttributeName;


        /// <summary>
        /// Inicializa la instancia
        /// </summary>
        /// <param name="ConnectionString">Cadena de conexión al ADAM</param>
        /// <param name="ApplicationName">Nombre de aplciación a la que conectarse</param>
        /// <param name="UserNameToLogon">Nombre de usuario ADAM con el que realizar Logon</param>
        /// <param name="PasswordToLogon">Password de usuario ADAM con el que realizar Logon</param>
        /// <param name="UserAttributeName">Atributo de conexión</param>
        public AzManMgr(string ConnectionString, string ApplicationName, string UserNameToLogon, string PasswordToLogon, string UserAttributeName)
        {
            if (string.IsNullOrEmpty(ConnectionString))
                throw new Exception("ConnectionString Empty");

            this._ConnectionString = ConnectionString;
            this._ApplicationName = ApplicationName;
            this._UserNameToLogon = UserNameToLogon;
            this._PasswordToLogon = PasswordToLogon;
            this._UserAttributeName = UserAttributeName;
        }


        /// <summary>
        /// Devuelve una entrada en ADAM
        /// </summary>
        /// <returns></returns>
        internal DirectoryEntry getADAMEntry()
        {
            // Conectamos con el ADAM
            AuthenticationTypes connectionType = AuthenticationTypes.None;
            DirectoryEntry adam = null;
            if(string.IsNullOrEmpty(this._UserNameToLogon))
                adam = new DirectoryEntry(this._ConnectionString,
                  null,
                  null,
                  connectionType
                  );
            else
                adam = new DirectoryEntry(this._ConnectionString,
                  this._UserNameToLogon,
                  this._PasswordToLogon,
                  connectionType
                  );



            return adam;
        }

        /// <summary>
        /// Devuelve una entrada a la aplicación en ADAM
        /// </summary>
        /// <returns></returns>
        internal DirectoryEntry getApplicationEntry()
        {
            // Conectamos con el ADAM
            DirectoryEntry adam = getADAMEntry();

            // Buscamos la aplicación
            DirectorySearcher searchApp = new DirectorySearcher(adam);
            searchApp.Filter = string.Format("(&(objectClass={0})({1}={2}))", OBJECT_APPLICATION, ATTRIBUTE_APPLICATION_NAME, this._ApplicationName);
            SearchResult resultApp = searchApp.FindOne();
            if (resultApp == null)
                throw new Exception("Application not found");

            // Path de la aplicación en ADAM
            string applicationPAth = (string)resultApp.Properties["distinguishedName"][0];

            // Entrada de la aplicación en ADAM
            return resultApp.GetDirectoryEntry();
        }


        /// <summary>
        /// Carga el perfil del usuario
        /// </summary>
        /// <param name="UserName">Nombre del usuario</param>
        /// <returns></returns>
        public UserProfileDefinition LoadUserProfileDefinition(string UserName)
        {
            try
            {
                if (string.IsNullOrEmpty(UserName))
                    return null;

                UserProfileDefinition res = new UserProfileDefinition(UserName);

                // Conectamos con el ADAM
                DirectoryEntry adam = getADAMEntry();

                // Entrada de la aplicación en ADAM
                DirectoryEntry applicationEntry = getApplicationEntry();

                // Buscamos en todos los grupos
                DirectorySearcher searchGroup = new DirectorySearcher(applicationEntry);
                searchGroup.Filter = string.Format("(&(objectClass={0}))", OBJECT_GROUP);
                SearchResultCollection resultGroup = searchGroup.FindAll();
                if (resultGroup == null)
                    return res; // No se han definido grupos en la aplicación

                foreach (SearchResult groupFilter in resultGroup)
                {
                    // Ejecutamos las búsquedas para ver que grupos cumple el usuario
                    if(groupFilter.Properties[ATTRIBUTE_GROUP_LDAPQUERY].Count > 0)
                    {
                        DirectorySearcher searchGroupEntry = new DirectorySearcher(adam);
                        searchGroupEntry.Filter = (string) groupFilter.Properties[ATTRIBUTE_GROUP_LDAPQUERY][0];
                        try
                        {
                            SearchResultCollection resultGroupEntry = searchGroupEntry.FindAll();
                            if (resultGroupEntry != null)
                            {
                                foreach (SearchResult groupEntryFilter in resultGroupEntry)
                                {
                                    if(groupEntryFilter.Properties[this._UserAttributeName].Count > 0)
                                    {
                                        if(groupEntryFilter.Properties[this._UserAttributeName][0].Equals(UserName))
                                        {
                                            // Usuario encontrado en el grupo actual del bucle

                                            // Buscamos las asignaciones de los roles que referencian a los grupos
                                            ResultPropertyValueCollection rolesPathIn = (ResultPropertyValueCollection)groupFilter.Properties[ATTRIBUTE_GROUP_ROLEASIGNADO];
                                            foreach(string rolePath in rolesPathIn)
                                            {
                                                DirectorySearcher searchRolAsignacion = new DirectorySearcher(applicationEntry);
                                                searchRolAsignacion.Filter = string.Format("(&(objectClass={0})(distinguishedName={1}))", OBJECT_ROLE, rolePath);
                                                SearchResult resultRolAsignacion = searchRolAsignacion.FindOne();
                                                if (resultRolAsignacion != null)
                                                {
                                                    if (resultRolAsignacion.Properties[ATTRIBUTE_ROLESASIGNADOS].Count > 0)
                                                        foreach (string pathRolHijo in resultRolAsignacion.Properties[ATTRIBUTE_ROLESASIGNADOS])
                                                        {
                                                            Role rol = LoadRoleFromPath(pathRolHijo, applicationEntry);

                                                            // Añadimos el rol al perfil del usuario
                                                            if(!res.Roles.ContainsKey(rol.Name))
                                                                res.Roles.Add(rol.Name, rol);
                                                        }
                                                }
                                            }
                                        }
                                    }
                                }                           
                            }
                        }
                        catch(Exception ex)
                        {
                            System.Diagnostics.EventLogPermission eventLogPermission = new System.Diagnostics.EventLogPermission(System.Diagnostics.EventLogPermissionAccess.Administer, ".");
                            eventLogPermission.PermitOnly();
                            System.Diagnostics.EventLog.WriteEntry("AzManADAMStore.AzManMgr", ex.Message);
                        }
                    }
                }

                return res;
            }
            catch(Exception ex)
            {
                System.Diagnostics.EventLogPermission eventLogPermission = new System.Diagnostics.EventLogPermission(System.Diagnostics.EventLogPermissionAccess.Administer, ".");
                eventLogPermission.PermitOnly();
                System.Diagnostics.EventLog.WriteEntry("AzManADAMStore.AzManMgr", ex.Message);
                throw new Exception("AzManADAMStore.AzManMgr.LoadUserProfileDefinition", ex);
            }

        }


        internal Role LoadRoleFromPath(string RolePath, DirectoryEntry EntryADAM)
        {
            try
            {
                DirectorySearcher searchRole = new DirectorySearcher(EntryADAM);
                searchRole.Filter = string.Format("(&(objectClass={0})(msDS-AzTaskIsRoleDefinition=TRUE)(distinguishedName={1}))", OBJECT_TASK, RolePath);
                SearchResult resultRole = searchRole.FindOne();
                if (resultRole == null)
                    return null;

                Role res = new Role();
                res.Path = RolePath;
                res.Name = (string)resultRole.Properties["cn"][0];
                res.Description = "";

                // Añado los roles hijos
                if (resultRole.Properties[ATTRIBUTE_ROLESHIJOS].Count > 0)
                    foreach (string pathRolHijo in resultRole.Properties[ATTRIBUTE_ROLESHIJOS])
                    {
                        Role rol = LoadRoleFromPath(pathRolHijo, EntryADAM);
                        if(rol != null)
                            // Añadimos el rol al perfil del usuario
                            if (!res.Roles.ContainsKey(rol.Name))
                                res.Roles.Add(rol.Name, rol);
                    }

                // Añado las tareas hijas
                if (resultRole.Properties[ATTRIBUTE_ROLESHIJOS].Count > 0)
                    foreach (string pathtaskHijo in resultRole.Properties[ATTRIBUTE_ROLESHIJOS])
                    {
                        Task task = LoadTaskFromPath(pathtaskHijo, EntryADAM);
                        if (task != null)
                            // Añadimos el rol al perfil del usuario
                            if (!res.Tasks.ContainsKey(task.Name))
                                res.Tasks.Add(task.Name, task);
                    }

                return res;
            }
            catch(Exception ex)
            {
                System.Diagnostics.EventLogPermission eventLogPermission = new System.Diagnostics.EventLogPermission(System.Diagnostics.EventLogPermissionAccess.Administer, ".");
                eventLogPermission.PermitOnly();
                System.Diagnostics.EventLog.WriteEntry("AzManADAMStore.AzManMgr", ex.Message);
                throw new Exception("AzManADAMStore.AzManMgr.LoadRoleFromPath", ex);
            }
        }

        internal Task LoadTaskFromPath(string TaskPath, DirectoryEntry EntryADAM)
        {
            try
            {
                DirectorySearcher searchtask = new DirectorySearcher(EntryADAM);
                searchtask.Filter = string.Format("(&(objectClass={0})(distinguishedName={1}))", OBJECT_TASK, TaskPath);
                SearchResult resultTask = searchtask.FindOne();
                if (resultTask == null)
                    return null;

                Task res = new Task();
                res.Path = TaskPath;
                res.Name = (string)resultTask.Properties["cn"][0];
                res.Description = "";

                // Añado las tareas hijas
                if (resultTask.Properties[ATTRIBUTE_ROLESHIJOS].Count > 0)
                    foreach (string pathtaskHijo in resultTask.Properties[ATTRIBUTE_ROLESHIJOS])
                    {
                        Task task = LoadTaskFromPath(pathtaskHijo, EntryADAM);
                        if (task != null)
                            // Añadimos el rol al perfil del usuario
                            if (!res.Tasks.ContainsKey(task.Name))
                                res.Tasks.Add(task.Name, task);
                    }

                // Añadimos las operaciones
                if (resultTask.Properties[ATTRIBUTE_TASKASIGNADOS].Count > 0)
                    foreach (string pathOpHijo in resultTask.Properties[ATTRIBUTE_TASKASIGNADOS])
                    {
                        DirectorySearcher searchOp = new DirectorySearcher(EntryADAM);
                        searchOp.Filter = string.Format("(&(objectClass={0})(distinguishedName={1}))", OBJECT_OPERATION, pathOpHijo);
                        SearchResultCollection resultOp= searchOp.FindAll();
                        if (resultOp != null)
                        {
                            foreach (SearchResult opEntryFilter in resultOp)
                            {
                                Operation op = new Operation();
                                op.Path = pathOpHijo;
                                op.Name = (string)opEntryFilter.Properties["cn"][0];
                                op.Description = "";

                                if (!res.Operations.ContainsKey(op.Name))
                                    res.Operations.Add(op.Name, op);
                            }
                        }
                    }

                return res;
            }
            catch (Exception ex)
            {
                System.Diagnostics.EventLogPermission eventLogPermission = new System.Diagnostics.EventLogPermission(System.Diagnostics.EventLogPermissionAccess.Administer, ".");
                eventLogPermission.PermitOnly();
                System.Diagnostics.EventLog.WriteEntry("AzManADAMStore.AzManMgr", ex.Message);
                throw new Exception("AzManADAMStore.AzManMgr.LoadRoleFromPath", ex);
            }
        }




        /// <summary>
        /// Indica si el usuario pertence al rol indicado.
        /// Ejecuta la consulta contra el ADAM en cada llamada.
        /// </summary>
        /// <param name="username">Nombre del usuario</param>
        /// <param name="roleName">Rol a buscar</param>
        /// <returns></returns>
        public bool IsUserInRole(string username, string roleName)
        {
            try
            {
                if (string.IsNullOrEmpty(username))
                    return false;

                if (string.IsNullOrEmpty(roleName))
                    return false;


                // Conectamos con el ADAM
                DirectoryEntry adam = getADAMEntry();

                // Entrada de la aplicación en ADAM
                DirectoryEntry applicationEntry = getApplicationEntry();

                // Buscamos el rol
                DirectorySearcher searchRole = new DirectorySearcher(applicationEntry);
                searchRole.Filter = string.Format("(&(objectClass={0})(cn={1}))", OBJECT_ROLE, roleName);
                SearchResult resultRole = searchRole.FindOne();
                if (resultRole == null)
                    return false; // Rol no encontrado

                if (resultRole.Properties[ATTRIBUTE_ROLE_GROUPSASIGNADO].Count > 0)
                {
                    // Obtiene los grupos y ejecuta la consulta
                    foreach (string groupName in resultRole.Properties[ATTRIBUTE_ROLE_GROUPSASIGNADO])
                    {
                        DirectorySearcher searchGroup = new DirectorySearcher(applicationEntry);
                        searchGroup.Filter = string.Format("(&(objectClass={0})(distinguishedName={1}))", OBJECT_GROUP, groupName);
                        SearchResult resultGroup = searchGroup.FindOne();
                        if (resultGroup != null)
                        {
                            // Ejecutamos las búsquedas para ver que grupos cumple el usuario
                            DirectorySearcher searchGroupEntry = new DirectorySearcher(adam);
                            searchGroupEntry.Filter = (string)resultGroup.Properties[ATTRIBUTE_GROUP_LDAPQUERY][0];
                            try
                            {
                                SearchResultCollection resultGroupEntry = searchGroupEntry.FindAll();
                                if (resultGroupEntry != null)
                                {
                                    foreach (SearchResult groupEntryFilter in resultGroupEntry)
                                    {
                                        if (groupEntryFilter.Properties[this._UserAttributeName].Count > 0)
                                        {
                                            if (groupEntryFilter.Properties[this._UserAttributeName][0].Equals(username))
                                            {
                                                // Usuario encontrado en el grupo actual del bucle
                                                return true;
                                            }
                                        }
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                System.Diagnostics.EventLogPermission eventLogPermission = new System.Diagnostics.EventLogPermission(System.Diagnostics.EventLogPermissionAccess.Administer, ".");
                                eventLogPermission.PermitOnly();
                                System.Diagnostics.EventLog.WriteEntry("AzManADAMStore.AzManMgr", ex.Message);
                            }
                        }
                    }

                }

                return false;
            }
            catch (Exception ex)
            {
                System.Diagnostics.EventLogPermission eventLogPermission = new System.Diagnostics.EventLogPermission(System.Diagnostics.EventLogPermissionAccess.Administer, ".");
                eventLogPermission.PermitOnly();
                System.Diagnostics.EventLog.WriteEntry("AzManADAMStore.AzManMgr", ex.Message);
                throw new Exception("AzManADAMStore.AzManMgr.LoadUserProfileDefinition", ex);
            }
        }


        /// <summary>
        /// Devuelve un array con los nombres de los roles a los que tiene permiso un usuario.
        /// Ejecuta la consulta contra el ADAM en cada llamada.
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        public string[] GetRolesForUser(string UserName)
        {
            try
            {
                if (string.IsNullOrEmpty(UserName))
                    return null;

                Dictionary<string, string> res = new Dictionary<string, string>();

                // Conectamos con el ADAM
                DirectoryEntry adam = getADAMEntry();

                // Entrada de la aplicación en ADAM
                DirectoryEntry applicationEntry = getApplicationEntry();

                // Buscamos en todos los grupos
                DirectorySearcher searchGroup = new DirectorySearcher(applicationEntry);
                searchGroup.Filter = string.Format("(&(objectClass={0}))", OBJECT_GROUP);
                SearchResultCollection resultGroup = searchGroup.FindAll();
                if (resultGroup == null)
                    return new string[0]; // No se han definido grupos en la aplicación

                foreach (SearchResult groupFilter in resultGroup)
                {
                    // Ejecutamos las búsquedas para ver que grupos cumple el usuario
                    if (groupFilter.Properties[ATTRIBUTE_GROUP_LDAPQUERY].Count > 0)
                    {
                        DirectorySearcher searchGroupEntry = new DirectorySearcher(adam);
                        searchGroupEntry.Filter = (string)groupFilter.Properties[ATTRIBUTE_GROUP_LDAPQUERY][0];
                        try
                        {
                            SearchResultCollection resultGroupEntry = searchGroupEntry.FindAll();
                            if (resultGroupEntry != null)
                            {
                                foreach (SearchResult groupEntryFilter in resultGroupEntry)
                                {
                                    if (groupEntryFilter.Properties[this._UserAttributeName].Count > 0)
                                    {
                                        if (groupEntryFilter.Properties[this._UserAttributeName][0].Equals(UserName))
                                        {
                                            // Usuario encontrado en el grupo actual del bucle

                                            // Buscamos la asignación de roles que referencian a los grupos
                                            ResultPropertyValueCollection rolesPathIn = (ResultPropertyValueCollection)groupFilter.Properties[ATTRIBUTE_GROUP_ROLEASIGNADO];
                                            foreach (string rolePath in rolesPathIn)
                                            {
                                                DirectorySearcher searchRol = new DirectorySearcher(applicationEntry);
                                                searchRol.Filter = string.Format("(&(objectClass={0})(distinguishedName={1}))", OBJECT_ROLE, rolePath);
                                                SearchResult resultRol = searchRol.FindOne();
                                                if (resultRol != null)
                                                {
                                                    // Añadimos todos sus roles hijos
                                                    if (resultRol.Properties[ATTRIBUTE_ROLESASIGNADOS].Count > 0)
                                                        foreach (string pathRolHijo in resultRol.Properties[ATTRIBUTE_ROLESASIGNADOS])
                                                            GetSubRolesForUser(UserName, res, pathRolHijo, applicationEntry);
                                                }

                                            }
                                        }
                                    }
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            System.Diagnostics.EventLogPermission eventLogPermission = new System.Diagnostics.EventLogPermission(System.Diagnostics.EventLogPermissionAccess.Administer, ".");
                            eventLogPermission.PermitOnly();
                            System.Diagnostics.EventLog.WriteEntry("AzManADAMStore.AzManMgr", ex.Message);
                        }
                    }
                }

                
                return (new List<string>(res.Values)).ToArray();
            }
            catch (Exception ex)
            {
                System.Diagnostics.EventLogPermission eventLogPermission = new System.Diagnostics.EventLogPermission(System.Diagnostics.EventLogPermissionAccess.Administer, ".");
                eventLogPermission.PermitOnly();
                System.Diagnostics.EventLog.WriteEntry("AzManADAMStore.AzManMgr", ex.Message);
                throw new Exception("AzManADAMStore.AzManMgr.GetRolesForUser", ex);
            }
        }

        /// <summary>
        /// Carga el nombre del rol y sus hijos en la lista.
        /// En caso de error puede que lance una excepción.
        /// </summary>
        /// <param name="UserName"></param>
        /// <param name="Roles"></param>
        /// <param name="RolePath"></param>
        internal void GetSubRolesForUser(string UserName, Dictionary<string, string> Roles, string RolePath, DirectoryEntry ApplicationEntry)
        {
            DirectorySearcher searchSubRol = new DirectorySearcher(ApplicationEntry);
            searchSubRol.Filter = string.Format("(&(objectClass={0})(msDS-AzTaskIsRoleDefinition=TRUE)(distinguishedName={1}))", OBJECT_TASK, RolePath);
            SearchResult resultSubRol = searchSubRol.FindOne();
            if (resultSubRol != null)
            {
                // Añado el rol en la lista
                string roleName = (string)resultSubRol.Properties["cn"][0];
                if(!Roles.ContainsKey(roleName))
                {
                    Roles.Add(roleName, roleName);

                    // Añado los roles hijos
                    if (resultSubRol.Properties[ATTRIBUTE_ROLESHIJOS].Count > 0)
                        foreach (string pathRolHijo in resultSubRol.Properties[ATTRIBUTE_ROLESHIJOS])
                            GetSubRolesForUser(UserName, Roles, pathRolHijo, ApplicationEntry);
                }
            }
        }
    }
}
